Multiple Events onChange with if/else statement - reactjs

I'm using a bootstrap form slider from 1-5 to describe the condition of something from good - bad. On the slider change, I want it to envoke two functions; one to change the number in the state, and then one to identify the correct text to go with the number and display it as the user slides the slider. I've tried a few different variations of this, but nothing seems to work. Would also welcome other ways of forming the functions or state all together. Thanks in advance.
class ConditionSlider extends Component {
constructor (props) {
super(props);
this.state = {
condition: 1
};
};
render() {
const slideCalls = (e) => {
slideChangeState(e);
conditionText(e)
};
const slideChangeState = (e) => {
this.setState({
condition: e
})
}
let spanText;
const conditionText = (e) => {
if(e === '1' ) {
spanText = <div>Very Poor</div>
} else if (e === '2') {
spanText = <div>Poor</div>
} else if (e === '3') {
spanText = <div>Okay</div>
} else if (e === '4') {
spanText = <div>Good</div>
} else if (e === '5') {
spanText = <div>Excellent</div>
}
}
console.log(this.state.condition)
return (
<div className="slide-class">
<RangeSlider
value={this.state.condition}
onChange={e => slideCalls(e.target.value)}
min={1}
max={5}
step={1}
size='sm'
tooltip='off'
variant='primary'
/>
<p>{this.spanText}</p>
<p>{spanText}</p>
</div>
)}
};

You need to modify several things first to make it work. First, the render function should not contain those two functions. Then, you could have an object that contains the numbers as keys (1 to 5) and the string that correlates with that number (Poor, excelent, etc.) so you don't fallback to a large if...else statement.
So leaving it as this:
const RANGES = {
1: "Very Poor",
2: "Poor",
3: "Okay",
4: "Good",
5: "Excellent",
}
class ConditionSlider extends Component {
constructor (props) {
super(props);
this.state = {
condition: 1
};
};
slideCalls(e) {
slideChangeState(e);
conditionText(e)
};
slideChangeState(e) {
this.setState({
condition: e
})
}
render() {
return (
<div className="slide-class">
<RangeSlider
value={this.state.condition}
onChange={e => slideCalls(e.target.value)}
min={1}
max={5}
step={1}
size='sm'
tooltip='off'
variant='primary'
/>
<p>{RANGES[this.state.condition]}</p> // removed <div> here since you cant have a div inside a <p> element. I mean, you could but it would display an error in the console
<p>{spanText}</p>
</div>
)}
};

I'm coding this blind, but this is how I'd handle this situation. If you're using state to manage the value of the slider then you ,ight as well use state to hold the text value too.
I've moved the functions outside of the render method, and as slideChangeState requires access to this I've bound it in the constructor.
I hope this helps - never used this control before
class ConditionSlider extends Component {
constructor(props) {
super(props);
this.state = {
condition: 1,
spanText: "Very Poor"
};
this.slideChangeState = this.slideChangeState.bind(this);
};
slideChangeState(value) {
var spanText = conditionText(value);
this.setState({
condition: value,
spanText: spanText
})
};
conditionText(value) {
switch (value) {
case 1:
return "Very Poor";
case 2:
return "Poor";
case 3:
return "Okay";
case 4:
return "Good";
case 5:
return "Excellent"
}
}
render() {
return (
<div className="slide-class">
<RangeSlider
value={this.state.condition}
onChange={e => slideCalls(e.target.value)}
min={1}
max={5}
step={1}
size='sm'
tooltip='off'
variant='primary'
/>
<p>{this.state.spanText}</p>
</div>
)
}
};

Related

React: Cannot show button when element is true

this is my code:
const Agregar = ({inputPregunta, validatePregunta}) => {
return(
<div id="content">
<h2>¿Cual es tu pregunta?</h2>
<input id="valorPregunta" type="text" placeholder="Añade aqui tu pregunta..." onChange={(e) => inputPregunta(e)}/>
{validatePregunta && <button>Agregar</button>}
</div>
);
}
What i am trying to do is when the input has something entered the prop validatePregunta (default is false) comes to true and the button element shows, for that i tried to do a method in the App.js file like this:
actualizarPregunta = (e) => {
this.setState({inputPregunta: e.target.value})
if(this.state.inputPregunta.trim().length > 0){
this.setState({validatePregunta: true})
} else {
this.setState({validatePregunta: false})
}
}
But nothing shows, is there's something that i am doing wrong?
Edit: Here is the code of the rendering for the props:
renderRoute = () => {
switch(this.state.route) {
case 'menu':
return (
<div>
<Header />
<Agregar inputPregunta={this.actualizarPregunta} validate={this.state.validatePregunta}/>
<Publications />
</div>
)
default :
return (
<div>
<Header />
<Publications />
</div>
)
}
}
render() {
return (
<div>
{
this.renderRoute(this.state.route)
}
</div>
);
}
This is how you use the compoennt
<Agregar inputPregunta={this.actualizarPregunta} validate={this.state.validatePregunta}/>
you are passing the value of this.state.validatePregunta by property name of validate
and then you are expecting something validatePregunta in component Agregar, but it should be actually validate
const Agregar = ({inputPregunta, validatePregunta}) => { //incorrect
const Agregar = ({inputPregunta, validate}) => { //correct
OR else simply change the prop name as below
<Agregar inputPregunta={this.actualizarPregunta} validatePregunta={this.state.validatePregunta}/>
And you should change actualizarPregunta once you update the state it's not updating the state value real time, its a async process, so this.state.inputPregunta.trim() will give you the value just before the update, so change it like this, and i love the way #Drew Reese handle this part
actualizarPregunta = (e) => {
const newValue = e.target.value;
this.setState({inputPregunta: newValue })
if(newValue.trim().length > 0){
this.setState({validatePregunta: true})
} else {
this.setState({validatePregunta: false})
}
}
Issue 1
The props don't align.
The component signature is const Agregar = ({inputPregunta, validatePregunta}) but you pass validate={this.state.validatePregunta}.
<Agregar
inputPregunta={this.actualizarPregunta}
validate={this.state.validatePregunta}
/>
Solution
Align on prop name usage.
<Agregar
inputPregunta={this.actualizarPregunta}
validatePregunta={this.state.validatePregunta}
/>
Issue 2
React component state lifecycle. The enqueued state is attempted to be referenced within the same react cycle that enqueue it. You need the enqueued value which won't be available until the next render cycle.
actualizarPregunta = (e) => {
this.setState({ inputPregunta: e.target.value }); // <-- state update enqueued
if (this.state.inputPregunta.trim().length > 0){ // <-- current state value
this.setState({ validatePregunta: true })
} else {
this.setState({ validatePregunta: false })
}
}
Solution 1
Enqueue the state update and use the current event value to set the other state
actualizarPregunta = (e) => {
const { value } = e.target;
this.setState({
inputPregunta: value,
validatePregunta: !!value.trim().length, // <-- *
});
}
* length == 0 is falsey, length !== 0 is truthy, and coerced to boolean (!!)
Solution 2
Enqueue the state update and use componentDidUpdate lifecycle function to handle effect
actualizarPregunta = (e) => {
const { value } = e.target;
this.setState({ inputPregunta: value });
}
componentDidUpdate(prevProps, prevState) {
const { inputPregunta } = this.state;
if (prevState.inputPregunta !== inputPregunta) {
this.setState({ validatePregunta: !!inputPregunta.trim().length });
}
}
Solution #1 is the simpler and more straight forward.

React: length validation, maximum update depth exceeded

Fairly new to coding, and especially to React. Have seen other similar questions but can't seem to apply the answers to my code.
I'm trying to update the 'validate' state when the input length reaches 5 and I get the 'maximum update depth exceeded' error. From what I understand when the length reaches 5 it re-renders the DOM, finds that the length = 5 and so begins to call itself recursively (correct me if I'm wrong!), and so I'm trying to work out how to execute validationHandler() when the number reaches 5 only once.
class App extends Component {
state = {
inputLength: '0',
validate: 'Too short'
}
changeHandler = (event) => {
this.setState({ inputLength: event.target.value.length });
};
validationHandler = () => {
if (this.state.inputLength > 4) {
this.setState({ validate: "Enough"})
}
};
render() {
return (
<div className="App">
<input
type="text"
onChange={this.changeHandler.bind(this)}
/>
<Validation
change={this.validationHandler()}
validate={this.state.validate}/>
</div>
);
}
}
I have a separate validation component.
const validation = (props) => {
return (
<div className="validation">
<p onChange={props.change}>{props.validate}</p>
</div>
)
};
Thanks in advance!
You have exactly the same issue as ReactJS: Maximum update depth exceeded error.
However, while applying the solution, i.e.
<Validation
change={this.validationHandler}
validate={this.state.validate}/>
will fix the error, it won't make your app work. The validationHandler method will never be called because p elements do not trigger a change event.
<p onChange={props.change}>{props.validate}</p>
Instead you want to validate the input length whenever the input changes, so that should happen inside the changeHandler method:
changeHandler = (event) => {
this.setState({
inputLength: event.target.value.length, // remove if not needed
validate: event.target.value.length > 4 ? "Enough" : "Too short",
});
};
and
render() {
return (
<div className="App">
<input
type="text"
onChange={this.changeHandler}
/>
<Validation validate={this.state.validate}/>
</div>
);
}
Calling .bind(this) has no effect because this.changeHandler is an arrow function.
Change the code to below
class App extends Component {
state = {
validate: 'Too short'
}
changeHandler = (event) => {
if (event.target.value.length > 4) {
this.setState({ validate: "Enough"})
} else {
this.setState({ validate: "Too short"})
}
};
render() {
return (
<div className="App">
<input
type="text"
onChange={this.changeHandler.bind(this)}
/>
<Validation
validate={this.state.validate}/>
</div>
);
}
}
and component to
const validation = (props) => {
return (
<div className="validation">
<p>{props.validate}</p>
</div>
)
};

Reactjs show hide multiple components

Newbie React question here on show hide functionality.
I have a state of 'show' that I set to false:
this.state = {
show: false,
};
Then I use the following function to toggle
toggleDiv = () => {
const { show } = this.state;
this.setState({ show : !show })
}
And my display is
{this.state.show && <xxxxxx> }
This all works fine. However I want to apply the function it to multiple cases (similar to accordion, without the closing of other children. So I change my constructor to
this.state = {
show: [false,false,false,false,false,false]
};
and this to recognise there are 6 different 'shows'.
{this.state.show[0] && <xxxxxx> }
{this.state.show[1] && <xxxxxx> } etc
But where I get stuck is how to account for them in my toggleDiv function. How do I insert the square bracket reference to the index of show (if this is my problem)?
toggleDiv = () => {
const { show } = this.state;
this.setState({ show : !show })
}
Thanks for looking.
First of all I'd suggest you not to rely on current state in setState function, but to use the callback option to be 100% sure that you are addressing to the newest state:
this.setState((prevState) => ({ show: !prevState.show }));
How to deal with multiple elements?
You'll have to pass the index of currently clicked element.
{yourElements.map((elem, i) => <YourElem onClick={this.toggleDiv(i)} />)}
and then inside your toggleDiv function:
toggleDiv = (i) => () => {
this.setState((prevState) => {
const r = [...prevState.show]; // create a copy to avoid state mutation
r[i] = !prevState.show[i];
return {
show: r,
}
}
}
Use an array instead of a single value. In your toggle div function make a copy of the state array make necessary changes and push the entire array back up to state at the end.
This is some simplified code showing the workflow I described above
export default class myClass extends React.Component{
constructor(props){
super(props);
this.state = { show: new Array(2).fill(false) };
}
//you need a index or id to use this method
toggleDiv = (index) => {
var clone = Object.assign( {}, this.state.show ); //ES6 Clones Object
switch(clone[index]){
case false:
clone[index] = true
break;
case true:
clone[index] = false
break;
}
this.setState({ show: clone });
}
render(){
return(
<div>
{ this.state.show[0] && <div> First Div </div> }
{ this.state.show[1] && <div> Second Div </div> }
{ this.state.show[2] && <div> Third Div </div> }
</div>
)
}
}

Delay formatting input

I'm trying to find a way to make an editable number with decimals.
Requirements:
I want the value readable by other components. For this reason I store it in a parent state.
The value may be updated by a fetched value. Currently this happens for multiple variables in the parent component.
It doesn't matter if the actual value has more places as long as the input only shows x places.
I'm running into a problem when converting to the fixed value - specifically on Chrome which happens to be the browser of choice. I wrote up a codepen:
https://codepen.io/j1dopeman/pen/wQJNzQ
Only C is using the fixed value. It's stored in the parent state as 'places'. When trying to edit C it immediately converts it to the fixed value which moves the cursor and ruins the input. Backspace also doesn't work as expected. I've tried debouncing the change which didn't work - react won't show the change in the meantime and the second number will get messed up when it eventually updates. I've tried using local state but that interferes with an outside fetch propagating the values down and I think there were other problems too. I just want to enforce the decimal places but not immediately. Someone should be able to type 1.25 or backspace and type a new number with it doing the conversion like a second later.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
inputs: {
a: { val: 0 },
b: { val: 0 },
c: { val: 1.5, places: 2 },
d: { val: 0 },
e: { val: 0 },
f: { val: 0 }
}
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(value) {
const update = newVals => {
return state => {
let nv = {};
for (let [key, val] of Object.entries(newVals)) {
nv = { ...nv, [key]: Object.assign(state.inputs[key], val) };
}
const ni = Object.assign(state.inputs, nv);
return { inputs: ni };
};
};
//-----
this.setState(update(value));
}
render() {
return (
<div className="App">
<header className="App-header">
<h1>Calc</h1>
</header>
<InputArea
inputs={this.state.inputs}
onInputChange={this.handleInputChange}
/>
</div>
);
}
}
class InputArea extends React.Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(value) {
this.props.onInputChange(value);
}
render() {
const inputList = [];
for (let [key, value] of Object.entries(this.props.inputs)) {
inputList.push(
<Variable
key={key}
name={key}
value={value}
onInputChange={this.handleInputChange}
/>
);
}
return (
<div className="input">
<h1>Input</h1>
<div className="input-area">{inputList}</div>
</div>
);
}
}
class Variable extends React.Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(e) {
let v = this.props.value;
v.val = Number(e.target.value);
this.props.onInputChange({ [this.props.name]: v });
}
render() {
const label = this.props.name;
let val = this.props.value.val;
if (this.props.value.places !== undefined)
val = val.toFixed(this.props.value.places);
return (
<div className="flex-row">
<label>{label}</label>
<input
className="variable-input"
type="number"
name={label}
value={val}
step="any"
onChange={this.handleInputChange}
/>
</div>
);
}
}
So wrapping with parseFloat:
val = parseFloat(val.toFixed(this.props.value.places));
seems to not completely botch the input as the person is typing and backspace mostly works. So that's what I'm using for now. I would still like to know if there's a way to delay formatting an input.

How to use switch statement inside a React component?

I have a React component, and inside the render method of the component I have something like this:
render() {
return (
<div>
<div>
// removed for brevity
</div>
{ switch(...) {} }
<div>
// removed for brevity
</div>
</div>
);
}
Now the point is that I have two div elements, one at the top and one at the bottom, that are fixed. In the middle I want to have a switch statement, and according to a value in my state I want to render a different component. So basically, I want the two div elements to be fixed always, and just in the middle to render a different component each time. I'm using this to implement a multi-step payment procedure). Though, as is the code currently it doesn't work, as it gives me an error saying that switch is unexpected. Any ideas how to achieve what I want?
Try this, which is way cleaner too: Get that switch out of the render in a function and just call it passing the params you want. For example:
renderSwitch(param) {
switch(param) {
case 'foo':
return 'bar';
default:
return 'foo';
}
}
render() {
return (
<div>
<div>
// removed for brevity
</div>
{this.renderSwitch(param)}
<div>
// removed for brevity
</div>
</div>
);
}
In contrast to other answers, I would prefer to inline the "switch" in the render function. It makes it more clear what components can be rendered at that position. You can implement a switch-like expression by using a plain old javascript object:
render () {
return (
<div>
<div>
{/* removed for brevity */}
</div>
{
{
'foo': <Foo />,
'bar': <Bar />
}[param]
}
<div>
{/* removed for brevity */}
</div>
</div>
)
}
That's happening, because switch statement is a statement, but here javascript expects an expression.
Although, it's not recommended to use switch statement in a render method, you can use self-invoking function to achieve this:
render() {
// Don't forget to return a value in a switch statement
return (
<div>
{(() => {
switch(...) {}
})()}
</div>
);
}
I did this inside the render() method:
render() {
const project = () => {
switch(this.projectName) {
case "one": return <ComponentA />;
case "two": return <ComponentB />;
case "three": return <ComponentC />;
case "four": return <ComponentD />;
default: return <h1>No project match</h1>
}
}
return (
<div>{ project() }</div>
)
}
I tried to keep the render() return clean, so I put my logic in a 'const' function right above. This way I can also indent my switch cases neatly.
I'm not a big fan of any of the current answers, because they are either too verbose, or require you to jump around the code to understand what is going on.
I prefer doing this in a more react component centred way, by creating a <Switch/>. The job of this component is to take a prop, and only render children whose child prop matches this one. So in the example below I have created a test prop on the switch, and compared it to a value prop on the children, only rendering the ones that match.
Example:
const Switch = props => {
const { test, children } = props
// filter out only children with a matching prop
return children.find(child => {
return child.props.value === test
})
}
const Sample = props => {
const someTest = true
return (
<Switch test={someTest}>
<div value={false}>Will display if someTest is false</div>
<div value={true}>Will display if someTest is true</div>
</Switch>
)
}
ReactDOM.render(
<Sample/>,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You can make the switch as simple or as complex as you want. Don't forget to perform more robust checking of the children and their value props.
A way to represent a kind of switch in a render block, using conditional operators:
{(someVar === 1 &&
<SomeContent/>)
|| (someVar === 2 &&
<SomeOtherContent />)
|| (this.props.someProp === "something" &&
<YetSomeOtherContent />)
|| (this.props.someProp === "foo" && this.props.someOtherProp === "bar" &&
<OtherContentAgain />)
||
<SomeDefaultContent />
}
It should be ensured that the conditions strictly return a boolean.
lenkan's answer is a great solution.
<div>
{{ beep: <div>Beep</div>,
boop: <div>Boop</div>
}[greeting]}
</div>
If you need a default value, then you can even do
<div>
{{ beep: <div>Beep</div>,
boop: <div>Boop</div>
}[greeting] || <div>Hello world</div>}
</div>
Alternatively, if that doesn't read well to you, then you can do something like
<div>
{
rswitch(greeting, {
beep: <div>Beep</div>,
boop: <div>Boop</div>,
default: <div>Hello world</div>
})
}
</div>
with
function rswitch (param, cases) {
if (cases[param]) {
return cases[param]
} else {
return cases.default
}
}
Although this is yet another way to do it, if you have gone all-in on hooks, you could take advantage of useCallback to produce a function that is only recreated when necessary.
Let's say you have a component which should be rendered according to a status prop. With hooks, you could implement this as follows:
const MyComponent = ({ status }) => {
const renderContent = React.useCallback(() => {
switch(status) {
case 'CONNECTING':
return <p className="connecting">Connecting...</p>;
case 'CONNECTED':
return <p className="success">Connected Successfully!</p>
default:
return null;
}
}, [status]);
return (
<div className="container">
{renderContent()}
</div>
);
};
I like this because:
It's obvious what is going on - a function is created, and then later called (the immediately invoked anonymous function method looks a little odd, and can potentially confuse newer developers)
The useCallback hook ensures that the renderContent callback is reused between renders, unless the depedency status changes
The renderContent function uses a closure to access the necessary props passed in to the component. A separate function (like the accepted answer) requires the passing of the props into it, which can be burdensome (especially when using TypeScript, as the parameters should also be typed correctly)
You can do something like this.
<div>
{ object.map((item, index) => this.getComponent(item, index)) }
</div>
getComponent(item, index) {
switch (item.type) {
case '1':
return <Comp1/>
case '2':
return <Comp2/>
case '3':
return <Comp3 />
}
}
function Notification({ text, status }) {
return (
<div>
{(() => {
switch (status) {
case 'info':
return <Info text={text} />;
case 'warning':
return <Warning text={text} />;
case 'error':
return <Error text={text} />;
default:
return null;
}
})()}
</div>
);
}
You can't have a switch in render. The psuedo-switch approach of placing an object-literal that accesses one element isn't ideal because it causes all views to process and that can result in dependency errors of props that don't exist in that state.
Here's a nice clean way to do it that doesn't require each view to render in advance:
render () {
const viewState = this.getViewState();
return (
<div>
{viewState === ViewState.NO_RESULTS && this.renderNoResults()}
{viewState === ViewState.LIST_RESULTS && this.renderResults()}
{viewState === ViewState.SUCCESS_DONE && this.renderCompleted()}
</div>
)
If your conditions for which view state are based on more than a simple property – like multiple conditions per line, then an enum and a getViewState function to encapsulate the conditions is a nice way to separate this conditional logic and cleanup your render.
I really liked the suggestion in https://stackoverflow.com/a/60313570/770134, so I adapted it to Typescript like so
import React, { FunctionComponent } from 'react'
import { Optional } from "typescript-optional";
const { ofNullable } = Optional
interface SwitchProps {
test: string
defaultComponent: JSX.Element
}
export const Switch: FunctionComponent<SwitchProps> = (props) => {
return ofNullable(props.children)
.map((children) => {
return ofNullable((children as JSX.Element[]).find((child) => child.props['value'] === props.test))
.orElse(props.defaultComponent)
})
.orElseThrow(() => new Error('Children are required for a switch component'))
}
const Foo = ({ value = "foo" }) => <div>foo</div>;
const Bar = ({ value = "bar" }) => <div>bar</div>;
const value = "foo";
const SwitchExample = <Switch test={value} defaultComponent={<div />}>
<Foo />
<Bar />
</Switch>;
import React from 'react';
import ListView from './ListView';
import TableView from './TableView';
function DataView({
currView,
data,
onSelect,
onChangeStatus,
viewTodo,
editTodo,
deleteTodo,
}) {
return (
<div>
{(function () {
switch (currView) {
case 'table':
return (
<TableView
todos={data}
onSelect={onSelect}
onChangeStatus={onChangeStatus}
viewTodo={viewTodo}
editTodo={editTodo}
deleteTodo={deleteTodo}
/>
);
case 'list':
return (
<ListView
todos={data}
onSelect={onSelect}
onChangeStatus={onChangeStatus}
viewTodo={viewTodo}
editTodo={editTodo}
deleteTodo={deleteTodo}
/>
);
default:
break;
}
})()}
</div>
);
}
export default DataView;
Improved a bit from
Matt Way's answer.
export const Switch = ({ test, children }) => {
const defaultResult = children.find((child) => child.props.default) || null;
const result = children.find((child) => child.props.value === test);
return result || defaultResult;
};
export const Case = ({ children }) => children;
const color = getColorFromTheMostComplexFnEver();
<Switch test={color}>
<Case value="Green">Forest</Case>
<Case value="Red">Blood</Case>
<Case default>Predator</Case>
</Switch>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
How about:
mySwitchFunction = (param) => {
switch (param) {
case 'A':
return ([
<div />,
]);
// etc...
}
}
render() {
return (
<div>
<div>
// removed for brevity
</div>
{ this.mySwitchFunction(param) }
<div>
// removed for brevity
</div>
</div>
);
}
Switch-Case statement within React Component could be used as follows:
<div id="time-list">
{
(() => {
switch (groupByFilterId) {
case 0:/*Case 0 */
return (
<div>Case 0</div>
)
break;
case 1: /*Case 1 */
return (
<div>Case 1</div>
)
break;
case 2:/*Case 2 */
return (
<div>Case 2</div>
)
break;
}
})()}
</div>
make it easy and just use many if statements.
for example:
<Grid>
{yourVar==="val1"&&(<> your code for val1 </>)}
{yourVar==="val2"&&(<> your code for val2 </>)}
.... other statments
</Grid>
I converted accepted answer to arrow functional component solution and saw James provides similar answer and one can get error not defined. So here is the solution:
const renderSwitch = (param) => {
switch (param) {
case "foo":
return "bar";
default:
return "foo";
}
};
return (
<div>
<div></div>
{renderSwitch(param)}
<div></div>
</div>
);
Here is a full working example using a button to switch between components
you can set a constructor as following
constructor(props)
{
super(props);
this.state={
currentView: ''
}
}
then you can render components as following
render()
{
const switchView = () => {
switch(this.state.currentView)
{
case "settings": return <h2>settings</h2>;
case "dashboard": return <h2>dashboard</h2>;
default: return <h2>dashboard</h2>
}
}
return (
<div>
<button onClick={(e) => this.setState({currentView: "settings"})}>settings</button>
<button onClick={(e) => this.setState({currentView: "dashboard"})}>dashboard</button>
<div className="container">
{ switchView() }
</div>
</div>
);
}
}
As you can see I am using a button to switch between states.
I know I'm a bit late to the party, but I think this implementation might help
You can render the components using conditional operators instead
If you had the following switch statement
switch(value) {
case CASE1:
return <Case1Component/>
case CASE2:
return <Case2Component/>
case CASE3:
return <Case3Component/>
default:
return <DefaultComponent/>
}
You can convert it to react component like so
const cases = [CASE0, CASE1, CASE2]
// Reminds me of 'react-router-dom'
return (
<div>
{value === cases[0] && <Case0Component/>}
{value === cases[1] && <Case1Component/>}
{value === cases[2] && <Case2Component/>}
{!cases.includes(value) && <DefaultComponent/>}
</div>
)
I am using this helper that allows me to have switch statements in JSX
// in helpers folder
const switchTrue = (object) => {
const { default: defaultValue, ...rest } = object;
const obj = { default: defaultValue, ...rest };
const result = Object.keys(obj).reduce((acc, cur) => {
return {
...acc,
[cur === 'default' ? 'true' : cur]: obj[cur],
};
}, {});
return result['true'];
};
const Sample = () => {
const isDataLoading = false;
return (
<div>
{
switchTrue({
[`${isDataLoading}`]: <div>Loading</div>,
[`${!isDataLoading}`]: <div>Data Ready</div>,
default: <div>Default</div>,
})
}
</div>
)
}
ReactDOM.render(
<Sample/>,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>
This helper should do the trick.
Example Usage:
{componentSwitch(3, (switcher => switcher
.case(1, () =>
<p>It is one</p>
)
.case(2, () =>
<p>It is two</p>
)
.default(() =>
<p>It is something different</p>
)
))}
Helper:
interface SwitchCases<T> {
case: (value: T, result: () => React.ReactNode) => SwitchCases<T>;
default: (result: () => React.ReactNode) => SwitchCases<T>;
}
export function componentSwitch<T>(value: T, cases: (cases: SwitchCases<T>) => void) {
var possibleCases: { value: T, result: () => React.ReactNode }[] = [];
var defaultResult: (() => React.ReactNode) | null = null;
var getSwitchCases: () => SwitchCases<T> = () => ({
case: (value: T, result: () => React.ReactNode) => {
possibleCases.push({ value: value, result });
return getSwitchCases();
},
default: (result: () => React.ReactNode) => {
defaultResult = result;
return getSwitchCases();
},
})
// getSwitchCases is recursive and will add all possible cases to the possibleCases array and sets defaultResult.
cases(getSwitchCases());
// Check if one of the cases is met
for(const possibleCase of possibleCases) {
if (possibleCase.value === value) {
return possibleCase.result();
}
}
// Check if the default case is defined
if (defaultResult) {
// Typescript wrongly assumes that defaultResult is always null.
var fixedDefaultResult = defaultResult as (() => React.ReactNode);
return fixedDefaultResult();
}
// None of the cases were met and default was not defined.
return undefined;
}
We can do this directly using useCallback
const renderContent = useCallback(() => {
switch (sortState) {
case 'one':
return 'ONE';
case 'two':
return 'TWO';
case 'three':
return 'THREE';
case 'four':
return 'FOUR';
default:
return 'ONE';
}
}, [sortState]);
This is to be used inside the jsx
<div>Sort:{renderContent()}</div>
This answer is specifically intended to address this "duplicate" question, by #tonyfat, regarding how to use conditional expressions to handle the same task.
Avoiding statements here seems like more trouble than it's worth, but this script does the job as the snippet demonstrates:
// Runs tests
let id = 0, flag = 0;
renderByFlag(id, flag); // jobId out of range
id = 1; // jobId in range
while(++flag < 5){ // active flag ranges from 1 to 4
renderByFlag(id, flag);
}
// Defines a function that chooses what to render based on two provided values
function renderByFlag(jobId, activeFlag){
jobId === 1 ? (
activeFlag === 1
? render("A (flag = 1)")
: activeFlag === 2
? render("B (flag = 2)")
: activeFlag === 3
? render("C (flag = 3)")
: pass(`flag ${activeFlag} out of range`)
)
: pass(`jobId ${jobId} out of range`)
}
// Defines logging functions for demo purposes
function render(val){ console.log(`Rendering ${val}`); }
function pass(reason){ console.log(`Doing nothing (${reason})`) }
const [route, setRoute] = useState(INITIAL_ROUTE)
return (
<RouteContext.Provider value={{ route, setRoute }}>
{(() => {
switch (route) {
case Route.Home:
return <PopupHomePage />
case Route.App:
return <PopupAppPage />
default:
return null
}
})()}
</RouteContext.Provider>
This is another approach.
render() {
return {this[`renderStep${this.state.step}`]()}
renderStep0() { return 'step 0' }
renderStep1() { return 'step 1' }

Resources