15

I've created a simple todo list to learn react and i'm trying to add some additional features. At the moment i'm trying to add buttons that toggle the list of items, so it either shows all the tasks or just those that are completed.

I've written a function to change the state of my visabilityFilter so I can later use this to toggle the items in the list, but it isn't behaving how it should be.

I console log the visabilityFilter variable but it always shows the wrong state before changing to the correct state. e.g. the 'show all' button will console log 'show completed' then if you press it again it will console log 'show all'

App.js

import React, { Component } from 'react';
import './App.css';
import TodoList from './components/TodoList.js'
import VisabilityFilter from './components/VisabilityFilter.js'

export const SHOW_ALL = 'show_all'
export const SHOW_COMPLETED = 'show_completed'

class App extends Component {
  constructor (props) {
    super(props)

    this.state = {
      inputValues: {
        'newTodo': ''
      },
      todos: [
        {
          task: 'My First Todo',
          completed: false
        }
      ],
      visabilityFilter: SHOW_ALL
    }

    this.addTodo = this.addTodo.bind(this)
    this.handleInputChange = this.handleInputChange.bind(this)
    this.handleKeyUp = this.handleKeyUp.bind(this)
    this.toggleCompleted = this.toggleCompleted.bind(this)
    this.removeTodo = this.removeTodo.bind(this)
    this.checkCompleted = this.checkCompleted.bind(this)
    this.setVisabilityFilter = this.setVisabilityFilter.bind(this)

  }

  handleInputChange (e) {
      const { inputValues } = this.state
      const { id, value } = e.target
      this.setState({
        inputValues: {
          ...inputValues,
          [id]: value
        }
      })
  }

  handleKeyUp (e) {
    var code = e.key
        if(code === 'Enter') {
          this.addTodo(e);
        }
  }

  toggleCompleted (e, index) {
    const { todos } = this.state

    todos[index].completed = !todos[index].completed

    todos.sort((a, b) => b.completed - a.completed)

    this.setState({ todos })
  }

  removeTodo (e, index) {
    const { todos } = this.state
    this.setState ({ todos: todos.filter((todo, i) => i !== index) })
  }

  addTodo (e) {
      const { todos, inputValues } = this.state
      const { dataset } = e.target

      if (inputValues[dataset.for] === '') return

      const newTodo = { task: inputValues[dataset.for], completed: false }
      todos.push(newTodo)

      this.setState({
        todos,
        inputValues: { ...inputValues, [dataset.for]: '' }
      })
  }

  checkCompleted (e, index) {
    const { todos } = this.state
    return { todos } && todos[index].completed
  }

  setVisabilityFilter (e) {
    const { visabilityFilter } = this.state
    const { dataset } = e.target

    this.setState({
      visabilityFilter: dataset.for
    })

    console.log ({ visabilityFilter })
  }

  render() {
    const { todos, inputValues, visabilityFilter } = this.state
    return (
      <div className="App">
        <TodoList
          todos={todos}
          inputValues={inputValues}
          addTodo={this.addTodo}
          handleInputChange={this.handleInputChange}
          removeTodo={this.removeTodo}
          toggleCompleted={this.toggleCompleted}
          handleKeyUp={this.handleKeyUp}
          checkCompleted={this.checkCompleted}
        />
        <VisabilityFilter setVisabilityFilter={this.setVisabilityFilter} />
      </div>
    );
  }
}

export default App;

VisabilityFilter.js

import React from 'react'
import { func } from 'prop-types'

import { SHOW_ALL, SHOW_COMPLETED } from '../App'

const VisabilityFilter = props => {
  return (
    <div>
      <button data-for={SHOW_COMPLETED} onClick={ props.setVisabilityFilter } >
        Show Completed Tasks
      </button>
      <button data-for={SHOW_ALL} onClick={ props.setVisabilityFilter }>
        Show All Tasks
      </button>
    </div>
  )
}

VisabilityFilter.propTypes = {
  setVisabilityFilter: func.isRequired
}

export default VisabilityFilter
Brian Le
  • 2,646
  • 18
  • 28
user2953989
  • 2,791
  • 10
  • 36
  • 49
  • 2
    The reason why console.log doesn't show the new state is that `setState()` is async. I can't find any problem in your code – Brian Le Feb 15 '19 at 16:40
  • Move your console.log into a function passed as the second parameter of `setState()` like this `this.setState({ visabilityFilter: dataset.for }, ()=>{console.log(this.state.visabilityFilter)} )` – The24thDS Feb 15 '19 at 16:44

3 Answers3

27

setState() is async (React docs), so the state changes won't be applied immediately. If you want to log out the new state,setState() takes in a function as the second argument and performs that function when the state is updated. So:

this.setState({ 
    abc: xyz 
  }, 
  () => console.log(this.state.abc),
)

Or you can also use componentDidUpdate(), which is recommended

Brian Le
  • 2,646
  • 18
  • 28
5

In the functional components, you can use useEffect to track changes in state.

  useEffect(() => {
      console.log(someState);
  },[someState);
Alija Fajic
  • 556
  • 6
  • 17
  • Edit: There is no need to put console log inside useEffect, you can just console log it anywhere in your component because your component will rerender every time the state changes. – Alija Fajic Sep 20 '22 at 06:53
3

The setState function in React is asynchronous, meaning that state updates may not happen immediately. If you want to access the updated state value, you can make use of the setState callback or the useEffect hook.

  1. Using the setState callback:
const [value, setValue] = useState(initialValue);

const handleClick = () => {
  setValue(newValue, () => {
    console.log(value); // Access the updated value here
  });
};

By providing a callback function as the second argument to setState, you can access the updated value after the state has been updated.

  1. Using the useEffect hook:
const [value, setValue] = useState(initialValue);

useEffect(() => {
  console.log(value); // Access the updated value here
}, [value]); // Specify "value" as the dependency

const handleClick = () => {
  setValue(newValue);
};

By utilizing the useEffect hook with the appropriate dependency, the code inside the effect function will execute whenever the value state changes, allowing you to access the updated value.

Remember that in both cases, the state updates are asynchronous, so the value logged in the console might not immediately reflect the updated state. However, by using the provided approaches, you can ensure you are accessing the most up-to-date value of the state in your code.

  • I am using react 18, the second option with `useEffect` working fine in my case. First option gives warning `Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().` Thanks – Abdul Basit Aug 07 '23 at 12:19