home.

tagged: reactjs

React: Animating component transition with react-spring and react-router

We can animate component transitions with react-spring's transitions. We'll use its react hooks implementation.
 
This is an explanation of: https://codesandbox.io/embed/jp1wr1867w
 
Let's first define our components along with a helper function to give it a full height style and background colour:
 
We'll use react-router's Link to go to a new component.

var pageStyle = (colour) => ({ height: "100%", backgroundColor: colour })

function A() {
  return <Link to="/b"><div style={pageStyle("lightblue")}>A</div></Link>
}
function B() {
  return <Link to="/c"><div style={pageStyle("pink")}>B</div></Link>
}
function C() {
  return <Link to="/a"><div style={pageStyle("lightgreen")}>C</div></Link>
}

We're going to use react's useContext hook and use react-router's __ReactContext. This gives us access to the current browser location. We'll use this to tell react-spring when it needs to transitions. We'll use this location, and location.pathname as the key for react-spring's transition:

const { location } = useContext(__RouterContext)
const transitions = useTransition(location, location => location.pathname, {
  initial: { transform: 'translate3d(0, 0%,0)'},
  from:    { transform: 'translate3d(0, 80%,0)'},
  enter:   { transform: 'translate3d(0, 0%,0)'},
  leave:   { transform: 'translate3d(0, -90%,0)'}
})

Finally we'll map over this transitions array, passing the key and props to animated.div and having the Switch within that. The animated.div must have absolute positioning to be able to animate it around the page.
 
Here's the whole thing:

import React, { useContext } from 'react';
import { useTransition, animated } from 'react-spring'
import { Route, BrowserRouter, Link, Switch, __RouterContext } from 'react-router-dom';
...
function App() {
  return (
    <BrowserRouter>
      <Home />
    </BrowserRouter>
  )
}

function Home() {
  const { location } = useContext(__RouterContext)
  const transitions = useTransition(location, location => location.pathname, {
    initial: { transform: 'translate3d(0, 0%,0)'},
    from: { transform: 'translate3d(0, 80%,0)'},
    enter: { transform: 'translate3d(0, 0%,0)'},
    leave: { transform: 'translate3d(0, -90%,0)'}
  })
  return (
    transitions.map(({ item, props, key}) => (
      <animated.div 
        style={{...props, position: "absolute", height: "100%", width: "100%"}} 
        key={key}>
        <Switch location={item}>
          <Route exact path="/" component={A} />
          <Route exact path="/a" component={A} />
          <Route exact path="/b" component={B} />
          <Route exact path="/c" component={C} />
        </Switch>
      </animated.div>
    ))
  )
}

var pageStyle = (colour) => ({ height: "100%", backgroundColor: colour })

function A() {
  return <Link to="/b"><div style={pageStyle("lightblue")}>A</div></Link>
}
function B() {
  return <Link to="/c"><div style={pageStyle("pink")}>B</div></Link>
}
function C() {
  return <Link to="/a"><div style={pageStyle("lightgreen")}>C</div></Link>
}

reactjs javascript


React: useReducer guide

React hooks introduced useReducer. This is an alternative to useState. And it comes from the redux world.
 
You give the reducer some state. Then based on actions (can be just names like 'add') it changes the state.
 
Let's first make a todo list example without a reducer, then afterward with a reducer, so we can see the difference.

Version without a reducer


function Todos() {
  const [textInput, setTextInput] = useState("")
  const [todos, setTodos] = useState([ 
    { item: "example1", done: false }, 
    { item: "example2", done: false }])
  const addTodo = () => {
    const newTodos = todos.slice()
    newTodos.push({ item: textInput, done: false })
    setTodos(newTodos)
  }
  const deleteNote = (index) => {
    const newTodos = todos.slice()
    newTodos.splice(index, 1)
    setTodos(newTodos)
  }
  const tickNote = (index) => {
    const newTodos = todos.slice()
    newTodos[index].done = !newTodos[index].done
    setTodos(newTodos)
  }
  return (
    <div>
      <input value={textInput} onChange={(e) => setTextInput(e.target.value)} />
      <button onClick={addTodo}>add</button>
      <div>
        {todos.map((item, i) => 
          <div key={i}>
            {item.item}
            <button onClick={() => tickNote(i)}>done? {""+item.done}</button>
            <button onClick={() => deleteNote(i)}>x</button>
          </div>
        )}
      </div>
    </div>
  )
}

We set two pieces of state. The first is to collect the <input> value. The second is our list of todo items.
 
Then the functions alter the todo list and update the state, and thus the user interface.
 
Now let's look at the version with a reducers.

Version with a reducer


The first thing note is that we have global state that includes both the todos and the input text:

var initialList = {
  textInput: "",
  todos: [
    { item: "example1", done: false }, 
    { item: "example2", done: false }
  ]
}

This is then passed to the reducer as the second argument -- giving it initial data:

  var [state, dispatch] = useReducer(..., 
    initialList)

The useReducer returns two things. The updated state and a dispatcher. The dispatcher will be used to send 'actions', e.g. dispatch(['delete', i]).
 
How do we handle these 'actions'? It's all in the first argument to useReducer:

var [state, dispatch] = useReducer((state, [action, payload]) => {
  switch(action) {
    case 'updateText': 
      return {...state, textInput: payload }
    case 'add': 
      state.todos.push({ item: state.textInput, done: false })
      return { ...state, textInput: "" }
    case 'delete': 
      state.todos.splice(payload, 1)
      return { ...state }
    case 'mark': 
      state.todos[payload].done = !state.todos[payload].done
      return { ...state }
    default: 
      throw new Error("bad action")
  }
}, initialList)

The first argument of useReducer takes a function. This function takes two parameters: the existing state and the argument to dispatch(...). (I'm destructuring this parameter for ease of use).
 
Then inside this function you use a switch statement with the 'action' you passed. And in our case something called a 'payload' which is the second item in the array that I will pass to dispatch.
 
So where is dispatch called? In our HTML:

<div>
  <input value={state.textInput} onChange={(e) => dispatch(["updateText", e.target.value])} />
  <button onClick={() => dispatch(['add'])}>add</button>
  <div>
    {state.todos.map((item, i) => 
      <div key={i}>
        {item.item}
        <button onClick={() => dispatch(['mark', i])}>done? {""+item.done}</button>
        <button onClick={() => dispatch(['delete', i])}>x</button>
      </div>
    )}
  </div>
</div>

Altogether our code looks like this:

function TodosReducer() {
  var initialList = {
    textInput: "",
    todos: [
      { item: "example1", done: false }, 
      { item: "example2", done: false }
    ]
  }
  var [state, dispatch] = useReducer((state, [action, payload]) => {
    switch(action) {
      case 'updateText': 
        return {...state, textInput: payload }
      case 'add': 
        state.todos.push({ item: state.textInput, done: false })
        return { ...state, textInput: "" }
      case 'delete': 
        state.todos.splice(payload, 1)
        return { ...state }
      case 'mark': 
        state.todos[payload].done = !state.todos[payload].done
        return { ...state }
      default: 
        throw new Error("bad action")
    }
  }, initialList)
  
  return (
    <div>
      <input value={state.textInput} onChange={(e) => dispatch(["updateText", e.target.value])} />
      <button onClick={() => dispatch(['add'])}>add</button>
      <div>
        {state.todos.map((item, i) => 
          <div key={i}>
            {item.item}
            <button onClick={() => dispatch(['mark', i])}>done? {""+item.done}</button>
            <button onClick={() => dispatch(['delete', i])}>x</button>
          </div>
        )}
      </div>
    </div>
  )
}

reactjs javascript


React: Context tutorial

Instead of passing data and functions around as props around you can use a context.
 
But first let's look an example app that doesn't use context. It uses props to communicate between components.
 
First we have a UserInfo component that displays a name:

function UserInfo({ name }) {
  return (
    <div>Your name is {name}</div>
  )
}

Then we have a ChooseUsername that on onChange in an input element calls a function that's passed in as a prop.

function ChooseUsername({ onChooseUsername }) {
  return (
    <div>
      <input placeholder="username" 
        onChange={(e) => onChooseUsername(e.target.value) } />
    </div>
  )
}

Finally we have the App component that threads those two components together: once ChooseUsername edits its input field then a function is called on App that sets some new state, which in turn updates the UserInfo component:

class App extends React.Component {
  state = {
    name: "Blank"
  }

  setName = (newName) => {
    this.setState({
      name: newName
    })
  }

  render() {
    return (
      <div>
        <ChooseUsername onChooseUsername={this.setName}></ChooseUsername>
        <hr />
        <UserInfo name={this.state.name}></UserInfo>
      </div>
    )  
  }
}

Now let's do the same again but with a react context.
 
Let's first look at our ChooseUsername and UserInfo components. I've given them a version 2 name.

function UserInfo2() {
  const state = useContext(App2Context);
  return (
    <div>Your name is {state.name}</div>
  )
}

function ChooseUsername2() {
  const state = useContext(App2Context);
  return (
    <div>
      <input placeholder="username" 
        onChange={(e) => state.onChooseUsername(e.target.value) } />
    </div>
  )
}

They no longer take in props. They get noth the name and onChooseUsername from the context that they get from useContext(App2Context). So what is App2Context? It's fairly simple:

const App2Context = React.createContext(null);

It contains no data at the moment. We want it to contain the state for our app:

 this.state = {
    name: "Blank",
    onChooseUsername: (newName) => {
      this.setState({
        name: newName
      })
    }
  } 

(Note I've put onChooseUsername in the state so the components can use it)
 
And we make the App2Context have this state through <App2Context.Provider>.
 
Let's look at our App2 class which uses the context:
 

const App2Context = React.createContext(null);

class App2 extends React.Component {
  state = {
    name: "Blank",
    onChooseUsername: (newName) => {
      this.setState({
        name: newName
      })
    }
  }
  render() {
    return (
      <App2Context.Provider value={this.state}>
        <ChooseUsername2></ChooseUsername2>
        <hr />
        <UserInfo2></UserInfo2>
      </App2Context.Provider>
    )  
  }
}

So UserInfo2 and ChoseUsername2 will use the context that's defined by <App2Context.Provider value={this.state}>. And when we call the onChooseUsername function it will update the state, and thus to context, and thus rerender the components in the tree.
 
So, why do this? Because passing around new props every time you add a new variable or function to your state is annoying.

reactjs javascript


React: Using React-Router intro

React-router is a library that lets you do routing in React. Let's use it.
 
Firstly, everything in your app must be surrounded by a BrowerRouter.
 
And you make links to different pages using <NavLink>. Let's make four links:

<div>
  <NavLink to="/">Main Page</NavLink>
  <NavLink to="/hello">Say Hello</NavLink>
  <NavLink to="/about/pink">About</NavLink>
  <NavLink to="/about/lightblue">About Blue</NavLink>
</div>

Note the third and fourth have url parameters. We'll use these later.
 
Finally, we must switch between the pages, so we use <Switch>. Inside that we use <Route>. And we specify a path which relates to the to attribute we used in the <NavLink>s (we also say it's exactly that - no fuzzy matching). Hext we have a component attribute we specifies the React component to render:

<Switch>
  ...
  <Route exact path="/" component={Main} />
  ...
</Switch>

If we want to use url parameters, and we do in the /about/pink and /about/lightblue links, we specify those in the path attribute of the <Route>. And then they appear in props.match.params in the component:

<Switch>
   ...
   <Route exact path="/about/:colour" component={About} />
   ...
</Switch>

....

function About({ match: { params } }) {
  return (
    <div style={{ backgroundColor: params.colour}}>
      I am the about page
    </div>
  )
}


Finally, if we want to pass props to our components, we use render instead of component. In render we return a component and you can pass a prop to that:

<Switch>
   ...
   <Route exact path="/hello" render={() => 
     <Hello name="Jakub"></Hello>
   } />
   ...
</Switch>


Here's the full example:
 

import { Route, BrowserRouter, NavLink, Switch } from 'react-router-dom';
...

function Main() {
  return (
    <div>I am the main page</div>
  )
}

function About({ match: { params } }) {
  return (
    <div style={{ backgroundColor: params.colour}}>
      I am the about page
    </div>
  )
}

function Hello({ name }) {
  return (
    <div>
      Hello, {name}
    </div>
  )
}

function App() {
  return (
    <BrowserRouter>
      <div>
        <div>
          <NavLink to="/">Main Page</NavLink>
          <NavLink to="/hello">Say Hello</NavLink>
          <NavLink to="/about/pink">About</NavLink>
          <NavLink to="/about/lightblue">About Blue</NavLink>
        </div>
        <hr />
        <Switch>
          <Route exact path="/" component={Main} />
          <Route exact path="/about/:colour" component={About} />
          <Route exact path="/hello" render={() => 
            <Hello name="Jakub"></Hello>
          } />
        </Switch>
      </div>
    </BrowserRouter>
  )
}



reactjs javascript


React: react-spring transitions tutorial

React-spring transitions let your transition items when they're added or removed from a list. Let's use them with react hooks.
 
Let's create an array of items (each has a key). An index variable which will be the successive keys. And two functions that will add or remove something from that list (we're using array slice to make new versions of the array so react see the change):

const [items, setList] = useState([
  {key: 1, item: "one"},
  {key: 2, item: "two"},
  {key: 3, item: "three"}
])
const [index, setIndex] = useState(4)
const addToList = () => {
  setIndex(index+1)
  var nItems = items.slice()
  nItems.push({key: index, item: "new"})
  setList(nItems) 
}
const removeFromList = () => {
  var nItems = items.slice()
  nItems.pop()
  setList(nItems) 
}

Next we pass these items to useTransition. And we'll specify where the keys are for this list of items (this is very importtant). And finally we'll specify the react-spring transitions state.

const transitions = useTransition(items, item => item.key, {
  initial: { transform: 'translate3d(0%, 0%,0)' },
  from: {  transform: 'translate3d(0%,-100%,0)' },
  enter: { transform: 'translate3d(0%, 0%,0)' },
  leave: { transform: 'translate3d(100%,0%,0)' }
})

initial is the initial position of the items. from is where they come from when they're added (100% above the final position in this case). enter is its resting place. And leave is where it will go when it's removed from the list (in our case we'll animate it off the screen)
 
Finally we render it like this:

<div>
  <button onClick={addToList}>add</button>
  <button onClick={removeFromList}>remove</button>
  {transitions.map(({ item, props, key }) =>
    <animated.div 
      key={key} 
      style={props}>
      <div>{item.item}</div>
    </animated.div>
  )}
</div>

We then map over transitions and show a animated.div, which takes in the app import props as style props and the key we defined in our list. We have our two buttons which add and remove items.
 
All the code looks like this:

import React, { useState } from 'react';
import { useTransition, animated } from 'react-spring'
...
function AThing2() {
  const [items, setList] = useState([
    {key: 1, item: "one"},
    {key: 2, item: "two"},
    {key: 3, item: "three"}
  ])
  const [index, setIndex] = useState(4)
  const addToList = () => {
    setIndex(index+1)
    var nItems = items.slice()
    nItems.push({key: index, item: "new"})
    setList(nItems) 
  }
  const removeFromList = () => {
    var nItems = items.slice()
    nItems.pop()
    setList(nItems) 
  }
  const transitions = useTransition(items, item => item.key, {
    initial: { transform: 'translate3d(0%, 0%,0)' },
    from: {    transform: 'translate3d(0%,-100%,0)' },
    enter: {   transform: 'translate3d(0%, 0%,0)' },
    leave: {   transform: 'translate3d(100%,0%,0)' }
  })
  return (
    <div>
      <button onClick={addToList}>add</button>
      <button onClick={removeFromList}>remove</button>
      {transitions.map(({ item, props, key }) =>
        <animated.div 
          key={key} 
          style={props}>
          <div>{item.item}</div>
        </animated.div>
      )}
    </div>
  )
}

![Demo](https://i.imgur.com/B2OIsm9.gif "Sup")

reactjs javascript

Page 1 of 4
next